package com.github.yoojia.flow;

import java.util.Arrays;
import java.util.Objects;

import static java.util.Objects.requireNonNull;

/**
 * @author 2017 Yoojia Chen (yoojiachen@gmail.com)
 */
public class Event {

    /**
     * 事件序列ID。仅在ReqRep模式中使用。
     * 它由内核自动设置，全局唯一。
     */
    public final long sequence;

    /**
     * 事件负载
     * // FIXME 无法保证payload字节组没有被修改。但如果复制的话。。。
     */
    public final byte[] payload;

    /**
     * 事件主题分类
     */
    public final String topic;

    /**
     * 发出此事件的来源地址
     */
    public final String sourceAddress;

    /**
     * 预期接收此事件的目标设备地址
     */
    public final String destAddress;

    /**
     * 事件发生的时间戳
     */
    public final long timestamp;

    /**
     * 作为远程事件时的配置信息
     */
    // transient，序列化时忽略此字段
    private transient Options mOptions;

    public Event(long sequence,
                 byte[] payload,
                 String topic,
                 String sourceAddress,
                 String destAddress,
                 long timestamp) {
        this.sequence = sequence;
        this.payload = requireNonNull(payload);
        this.topic = requireNonNull(topic);
        this.sourceAddress = requireNonNull(sourceAddress);
        this.destAddress = requireNonNull(destAddress);
        this.timestamp = timestamp;
    }

    public Options options() {
        if (mOptions == null) {
            mOptions = new Options(this);
        }
        return mOptions;
    }

    public Builder newBuilder() {
        return new Builder()
                .setSequence(sequence)
                .setPayload(payload)
                .setTopic(topic)
                .setSourceAddress(sourceAddress)
                .setDestAddress(destAddress)
                .setTimestamp(timestamp)
                .setOptions(options());
    }

    //// Utils

    /**
     * @return 返回当前Event对象的目标地址是否为远程地址
     */
    public boolean isRemoteDestAddress() {
        return destAddress != null &&
                destAddress.startsWith(Context.PREFIX_REMOTE_DESTINATION);
    }

    /**
     * @return 返回当前Event是否为从远程节点获取的Event
     */
    public boolean isReceivedFromRemote() {
        return !isReceivedFromLocal();
    }

    /**
     * @return 返回当前Event是否为从本地节点发起的Event
     */
    public boolean isReceivedFromLocal() {
        return mOptions == null ||
                !mOptions.isReceivedFromRemote();
    }

    //// get payloads

    public Payload.Bool getBoolPayload() {
        return Payload.Bool.create(payload);
    }

    public Payload.Float getFloatPayload() {
        return Payload.Float.create(payload);
    }

    public Payload.Bytes getBytesPayload() {
        return Payload.Bytes.create(payload);
    }

    public Payload.Int getIntPayload() {
        return Payload.Int.create(payload);
    }

    public Payload.Text getTextPayload() {
        return Payload.Text.create(payload);
    }

    ////

    public static Event simple(long sequence, String topic) {
        final Event event = create(sequence, new byte[0], topic, Context.ADDRESS_NONE, Context.ADDRESS_NONE, 0);
        return event;
    }

    /**
     * 创建Event对象
     *
     * @return Event
     */
    public static Event create(long sequence, byte[] payload, String topic, String sourceAddress, String destAddress, long timestamp) {
        return new Event(sequence, payload, topic, sourceAddress, destAddress, timestamp);
    }

    /**
     * 创建无固定目标的Event对象。
     *
     * @return Event
     */
    public static Event createNoneDestAddress(long sequence, byte[] payload, String topic, String sourceAddress, long timestamp) {
        return create(sequence, payload, topic, sourceAddress, Context.ADDRESS_NONE, timestamp);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Event event = (Event) o;
        return sequence == event.sequence &&
                timestamp == event.timestamp &&
                Arrays.equals(payload, event.payload) &&
                Objects.equals(topic, event.topic) &&
                Objects.equals(sourceAddress, event.sourceAddress) &&
                Objects.equals(destAddress, event.destAddress) &&
                Objects.equals(mOptions, event.mOptions);
    }

    @Override
    public int hashCode() {
        int result = Objects.hash(sequence, topic, sourceAddress, destAddress, timestamp, mOptions);
        result = 31 * result + Arrays.hashCode(payload);
        return result;
    }

    @Override
    public String toString() {
        return "Event{" +
                "sequence=" + sequence +
                ", payload=" + Arrays.toString(payload) +
                ", topic='" + topic + '\'' +
                ", sourceAddress='" + sourceAddress + '\'' +
                ", destAddress='" + destAddress + '\'' +
                ", timestamp=" + timestamp +
                ", options=" + mOptions +
                '}';
    }

    //////

    public static class Builder {

        private long mSequence;
        private byte[] mPayload;
        private String mTopic;
        private String mSourceAddress;
        private String mDestAddress;
        private long mTimestamp;
        private Options mOptions;

        public Builder setSequence(long sequence) {
            mSequence = sequence;
            return this;
        }

        public Builder setPayload(byte[] payload) {
            mPayload = payload;
            return this;
        }

        public Builder setTopic(String topic) {
            mTopic = topic;
            return this;
        }

        public Builder setSourceAddress(String sourceAddress) {
            mSourceAddress = sourceAddress;
            return this;
        }

        public Builder setDestAddress(String destAddress) {
            mDestAddress = destAddress;
            return this;
        }

        public Builder setTimestamp(long timestamp) {
            mTimestamp = timestamp;
            return this;
        }

        public Builder setOptions(Options options) {
            mOptions = options;
            return this;
        }

        public Event build() {
            final Event evt = create(mSequence, mPayload, mTopic, mSourceAddress, mDestAddress, mTimestamp);
            if (mOptions != null) {
                evt.options()
                        .copyOf(mOptions);
            }
            return evt;
        }
    }

    //////

    /**
     * MQTT相关选项
     */
    public static class Options {

        private final Event mRef;

        private int mMqttQoS = 1;
        private boolean mMqttRetained = true;

        private boolean mReceivedFromRemote = false;

        private Options(Event ref) {
            mRef = ref;
        }

        public Event event() {
            return mRef;
        }

        public Options copyOf(Event event) {
            if (event != null && event.mOptions != null) {
                return copyOf(event.mOptions);
            }
            return this;
        }

        public Options copyOf(Event.Options options) {
            if (options != null) {
                mMqttQoS = options.mMqttQoS;
                mMqttRetained = options.mMqttRetained;
                mReceivedFromRemote = options.mReceivedFromRemote;
            }
            return this;
        }

        /**
         * 设置事件对象的MQTT QoS值。当事件通过MQTT发送到远程节点时被使用。
         *
         * @param qos QoS
         * @return Options
         */
        public Options mqttQoS(int qos) {
            mMqttQoS = qos;
            return this;
        }

        public int mqttQoS() {
            return mMqttQoS;
        }

        /**
         * 设置事件对象的MQTT Retained值。当事件通过MQTT发送到远程节点时被使用。
         *
         * @param retained Retained
         * @return Options
         */
        public Options mqttRetained(boolean retained) {
            mMqttRetained = retained;
            return this;
        }

        public boolean mqttRetained() {
            return mMqttRetained;
        }

        ////

        /**
         * 当事件对象是从远程节点中获取时，自动设置此标记位。
         *
         * @param remote 标记位
         * @return Options
         */
        public Options receivedFromRemote(boolean remote) {
            mReceivedFromRemote = remote;
            return this;
        }

        /**
         * @return 返回消息是否为从远程节点中获取的标记
         */
        public boolean isReceivedFromRemote() {
            return mReceivedFromRemote;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Options options = (Options) o;
            return mMqttQoS == options.mMqttQoS &&
                    mMqttRetained == options.mMqttRetained &&
                    mReceivedFromRemote == options.mReceivedFromRemote &&
                    Objects.equals(mRef, options.mRef);
        }

        @Override
        public int hashCode() {
            return Objects.hash(mRef, mMqttQoS, mMqttRetained, mReceivedFromRemote);
        }

        @Override
        public String toString() {
            return "Options{" +
                    ", mqttQoS=" + mMqttQoS +
                    ", mqttRetained=" + mMqttRetained +
                    ", receivedFromRemote=" + mReceivedFromRemote +
                    '}';
        }
    }

}
